---
sidebar_position: 13
---

# Работа с ветками

## Ветвление

**Ветвление** — это возможность работать над разными версиями проекта: вместо одного списка с упорядоченными коммитами история будет расходиться в определённых точках. Каждая ветвь содержит легковесный указатель HEAD на последний коммит, что позволяет без лишних затрат создать много веток. Ветка по умолчанию называется master, но лучше назвать её в соответствии с разрабатываемой в ней функциональностью.

Итак, есть общий указатель `HEAD` и `HEAD` для каждой ветки. Переключение между ветками предполагает только перемещение `HEAD` в `HEAD` соответствующей ветки.

![Введение в ветвление](images/branches/branches-intro.jpg)

:::note Команда `git branch`

Получить список, создание или удаление ветвей.

Документация [здесь](https://git-scm.com/docs/git-branch).
:::

Команды:

`git branch <имя ветки>` — создаёт новую ветку с `HEAD`, указывающим на `HEAD`. Если не передать аргумент `<имя ветки>`, то команда выведет список всех локальных веток;
`git checkout <имя ветки>` — переключается на эту ветку. Можно передать опцию `-b`, чтобы создать новую ветку перед переключением;
`git branch -d <имя ветки>` — удаляет ветку.

Пример выполнения этих команд:

![Пример 1](images/branches/branches-example1.png)

Локальный и удалённый репозитории могут иметь немало ветвей, поэтому когда вы отслеживаете удалённый репозиторий — отслеживается удалённая ветка (`git clone` привязывает вашу ветку `main` или `master` к ветке `origin/main` или `origin/master` удалённого репозитория).

Привязка к удалённой ветке:

- `git branch -u <имя удалённого репозитория>/<удалённая ветка>` — привязывает текущую ветку к указанной удалённой ветке;
- `git checkout --track <имя удалённого репозитория>/<удалённая ветка>` — аналог предыдущей команды;
- `git checkout -b <ветка> <имя удалённого репозитория>/<удалённая ветка>` — создаёт новую локальную ветку и начинает отслеживать удалённую;
- `git branch --vv` — показывает локальные и отслеживаемые удалённые ветки;
- `git checkout <удалённая ветка>` — создаёт локальную ветку с таким же именем, как у удалённой, и начинает её отслеживать.

В общем, `git checkout` связан с изменением места, на которое указывает `HEAD` ветки, что похоже на то, как `git reset` перемещает общий `HEAD`.

## Прятки и чистка

Есть одна тонкость — при переключении веток Git требует, чтобы рабочее состояние было чистым, то есть все изменения в отслеживаемых файлах должны быть зафиксированы. При некоторых обстоятельствах Git может автоматически перенести незафиксированное изменение в другую ветку.

:::note Команда `git stash`

Скрыть изменения в "грязном рабочем" каталоге.

Документация [здесь](https://git-scm.com/docs/git-stash).
:::

Однако порой у вас есть незавершённые изменения, которые нельзя фиксировать. В такой ситуации их можно сохранить и "спрятать" с помощью команды `git stash`.

Пример выполнения `git stash`:

![Пример 2](images/branches/branches-example2.png)

Чтобы вернуть изменения, используйте `git stash apply`.

Возможно, вместо этого вы захотите стереть все спрятанные изменения. В таком случае используйте команду `git stash clear`.

## Слияние

Дадим определения:

- **Сливаемая ветка** – та ветка, с которой мы берем изменения, чтобы влить их в целевую.
- **Целевая ветка** – та ветка, в которую мы сливаем наши изменения.
- **Слияние веток** – это перенос изменений с одной ветки на другую. При этом слияние не затрагивает сливаемую ветку, то есть она остается в том же состоянии, что позволяет нам потом продолжить работу с ней.

Слияние включает в себя создание нового коммита, который основан на общем коммите-предке двух ветвей и указывает на оба `HEAD` в качестве предыдущих коммитов.

*Пример слияния (источник: [tproger.ru](https://tproger.ru/translations/beginner-git-cheatsheet/))*:

![Пример слияния](images/branches/merge-example.png)

:::note Команда `git merge`

Сливает изменения с переданной ветки в текущую.

Документация [здесь](https://git-scm.com/docs/git-merge).
:::

Для слияния мы переходим на основную ветку и используем команду `git merge <тематическая ветка>`.

Если обе ветви меняют одну и ту же часть файла, то возникает конфликт слияния — ситуация, в которой Git не знает, какую версию файла сохранить, поэтому разрешать конфликт нужно собственноручно. Чтобы увидеть конфликтующие файлы, используйте `git status`.

Пример с двумя ветками, в которых изменен один и тот же файл:

![Пример конфликта 1](images/branches/merge-conflict-example1.png)

Переходим в ветку `main` через `git checkout main` и делаем  `git merge new-branch`.

Статус репозитория:

![Пример конфликта 2](images/branches/merge-conflict-example2.png)

`git status` показывает:

```bash
On branch main
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
        both modified:   README

no changes added to commit (use "git add" and/or "git commit -a")
```

После открытия таких файлов вы увидите похожие маркеры разрешения конфликта:

![Пример конфликта 3](images/branches/merge-conflict-example3.png)

Замените в этом блоке всё на версию, которую вы хотите оставить, и подготовьте файл. После разрешения всех конфликтов можно использовать `git add` и `git commit` для завершения слияния.

## Перемещение

Вместо совмещения двух ветвей коммитом слияния, перемещение заново воспроизводит коммиты тематической ветки в виде набора новых коммитов базовой ветки, что выливается в более чистую историю коммитов.

*Пример перемещения (источник: [tproger.ru](https://tproger.ru/translations/beginner-git-cheatsheet/))*:

![Пример слияния](images/branches/rebase.png)

:::note Команда `git rebase`

Повторное применение коммитов поверх другого базового "конца".

Документация [здесь](https://git-scm.com/docs/git-rebase).
:::

Для перемещения используется команда `git rebase <основная ветка> <тематическая ветка>`, которая воспроизводит изменения тематической ветки на основной; `HEAD` тематической ветки указывает на последний воспроизведённый коммит.

## Перемещение vs. слияние

После слияния лог с историей может выглядеть довольно беспорядочно. С другой стороны, перемещение позволяет переписать историю в нормальной, последовательной форме. Но перемещение — не панацея от запутанных логов: перемещённые коммиты отличаются от оригинальных, хотя и имеют одного и того же автора, сообщение и изменения.

Сценарий:

- В своей ветке вы создаёте несколько коммитов и сливаете их в мастер-ветку.
- Кто-то ещё решает поработать на основе ваших коммитов.
- Вы решаете переместить ваши коммиты и отправить их на сервер.
- Когда кто-то попытается слить свою работу на основе ваших изначальных коммитов, в итоге мы получим две параллельные ветки с одним автором, сообщениями и изменениями, но разными коммитами.
- Перемещайте изменения только на вашей приватной локальной ветке — не перемещайте коммиты, от которых зависит ещё кто-то.

## Откат коммитов — revert и reset

:::note Команда `git revert`

Отменить некоторые существующие коммиты.

Документация [здесь](https://git-scm.com/docs/git-revert).
:::

:::note Команда `git reset`

Сброс текущей `HEAD` в указанное состояние.

Документация [здесь](https://git-scm.com/docs/git-reset).
:::

Похожие дебаты по поводу того, что лучше использовать, возникают, когда вы хотите откатить коммит. Команда `git revert <коммит>` создаёт новый коммит, отменяющий изменения, но сохраняющий историю, в то время как `git reset <коммит>` перемещает указатель `HEAD`, предоставляя более чистую историю (словно бы этого коммита никогда и не было). Важно отметить, что это также означает, что вы больше не сможете вернуться обратно к этим изменениям, например, если вы всё-таки решите, что отмена коммита была лишней. Чище — не значит лучше!

## Атрибуция

При подготовке статьи использован материал:

- [tproger.ru](https://tproger.ru/translations/beginner-git-cheatsheet/)
- [smartiqa.ru/courses/git](https://smartiqa.ru/courses/git/)